﻿using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Borg.AddOn;
using Borg.Properties;

namespace Borg
{
    [Serializable]
    public class Activator : RuntimeElement
    {
        private static readonly object Monitor = new object();
        private IActivatorSubstitutionHook _substitutionHook;

        internal static ExtensionContext ActivationContext;
        
        static Activator()
        { }

        protected internal Activator(Runtime runtime)
            : base(runtime)
        { }

        internal void Init(IActivatorSubstitutionHook substitutionHook)
        {
            _substitutionHook = substitutionHook;
        }

        protected internal virtual object CreateInstance(Extension extension)
        {
            var extensionContext = new ExtensionContext(extension);
            var extensionType = extension.ExtensionType;
            var plugin = extensionType.Plugin;
            
            var type =
                Loader.InternalGetType(Loader.InternalIsLoaded(plugin)
                ? Loader.InternalGetAssembly(plugin)
                : Loader.InternalLoad(plugin), extensionType.TypeName);
            
            object[] args;
            
            switch (extensionType.ConstructorType)
            {
                case ConstructorType.None:
                    args = null;
                    break;
                
                case ConstructorType.Extension:
                    args = new object[] { extension };
                    break;
                
                case ConstructorType.ExtensionContext:
                    args = new object[] { extensionContext };
                    break;
                
                default:
                    throw new ArgumentOutOfRangeException();
            }

            return CreateInstance(type, args, extensionContext);
        }

        protected internal object CreateInstance(Type type, object[] args, Extension extension)
        {
            return CreateInstance(type, args, new ExtensionContext(extension));
        }

        protected internal object CreateInstance(Type type, object[] args, ExtensionContext extensionContext)
        {
            lock (Monitor)
            {
                var context = ActivationContext;
                
                try
                {
                    if (type.IsSubclassOf(typeof(ExtensionBase)))
                    {
                        Debug.Assert(ActivationContext == null);
                        ActivationContext = extensionContext;
                    }

                    object arguments;
                    
                    if (!_substitutionHook.Create(extensionContext.Extension, out arguments))
                        arguments =
                            System.Activator.CreateInstance(
                                type,
                                BindingFlags.Instance |
                                BindingFlags.Public |
                                BindingFlags.NonPublic |
                                BindingFlags.CreateInstance,
                                null,
                                args,
                                null);
                    
                    _substitutionHook.SubstituteExtensionObject(extensionContext.Extension, ref arguments);
                    return arguments;
                }
                catch (Exception ex)
                {
                    var notFound = ex.InnerException as FileNotFoundException;

                    if (notFound != null)
                    {
                        Logger.Log(
                            VerbosityLevel.Normal,
                            LogType.Error,
                            this,
                            Messages.Composer_ErrorOnActivatingExtension,
                            (object) extensionContext.Extension.Id,
                            (object) extensionContext.Extension.Name,
                            (object) string.Format(
                                Messages.Composer_Error_AssemblyNotFound,
                                notFound.FileName));
                        
                        ex.Data["IsLogged"] = true;
                    }

                    throw;
                }
                finally
                {
                    ActivationContext = context;
                }
            }
        }

        public static T Create<T>(string typeName, params object[] args)
        {
            return Create<T>(typeName, typeof(T), args);
        }

        public static T Create<T>(string typeName, Type defaultType, params object[] args)
        {
            var type =
                string.IsNullOrEmpty(typeName)
                ? defaultType
                : Type.GetType(typeName);
            
            if (type == null)
                throw new ArgumentException(
                    string.Format(
                    "Cannot create instance of type '{0}':{1}  Type not found",
                    typeName, Environment.NewLine));
            
            try
            {
                return (T) System.Activator.CreateInstance(
                    type,
                    BindingFlags.Instance |
                    BindingFlags.Public |
                    BindingFlags.NonPublic |
                    BindingFlags.CreateInstance,
                    null,
                    args,
                    null);
            }
            catch (Exception ex)
            {
                throw new ArgumentException(
                    string.Format(
                        "Cannot create instance of type '{0}':{1}  {2}",
                        type.Name, Environment.NewLine, ex.Message), ex);
            }
        }
    }
}
